package SM2;

import cn.com.zoe.crypto.sm.Util;
import com.zoe.phip.infrastructure.util.StringUtil;
import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.math.ec.ECPoint;

import java.io.IOException;
import java.math.BigInteger;

/**
 * Created by Administrator on 2021/9/7.
 */
public class SM2Util {
    private static X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1");
    private static ECDomainParameters ecDomainParameters = new ECDomainParameters(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN());
    private static ECParameterSpec ecParameterSpec = new ECParameterSpec(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN());

    public static void main(String[] args) throws Exception
    {
        ////// TODO: 随机生成一个guid() ,并转成byte类型
        String plainText = StringUtil.getUUID();

        byte[] sourceData = plainText.getBytes("UTF-8");
        // 私钥
        String prik = "F271B387457DA7E5E1259A7E8735E1CF7722F08AB3D393D9E942E5AE30167A81";
        // 公钥
        String pubk = "04DE1F6132007A9CB1E16A44DAECB824443ADC85F5FE09C4CD28DE1ADCBF8C776BEEB9D5D9DD83CBF48047B111F0EF74FA02EB758253EBBA684E5A231FB9ADD3BB";
        ////// TODO: 公钥先转小写，再转字符，最后转成十六进制字节
        char[] charStr=pubk.toLowerCase().toCharArray();
        byte[] publicKey= Util.decodeHex(charStr);
        ////// TODO: 加密处理
        String cipherText = encrypt(publicKey, sourceData);
        System.out.println("加密: "+cipherText);

        ////// TODO: 解密 第一步：先把私钥转小写，再转字符，最后转十六进制字节
        char[] charPri=prik.toLowerCase().toCharArray();
        byte[] privateKey=Util.decodeHex(charPri);
        // String cipherText="BP58uTNX2v7yRwvC7rtpoMKpUTv89UzUUc8W/zWCWEJ/5kCiuzRXR01usY1VV9DV9dni0xDj9snoBOaMpSG3dnnjVthZjckWXKBscFQ6GhsbhQ00ABSRb4ydylBrWlRacrW7vd4m/KfIUnYI+GzUNbGXA2Jp0PZ00DGyVn8jTMC7";
        cipherText="BNUc+v7TbsuSWMP1l3RQNBLe7ec5y6CiJr/CXXkMXdw6q/bAwN+bWViLt+WHKK3tgFAlgULXZn21mJjjmXJjadSGTnRTAEeKRdNTSN02Z3JBwQQPPZiDULxL0xr0GTS7oinMwZjsDvDy88DprqxEHeTNxXMmpjCAQ+fmZUjJCggY";
        ////// TODO: 解密
        byte[] decrypt=decrypt(privateKey, Base64.decodeBase64(cipherText));
        String result=new String(decrypt);
        System.out.println("解密: "+result);

    }

    /**
     * SM2加密
     * @param publicKey
     * @param data
     * @return
     * @throws InvalidCipherTextException
     */
    public static String encrypt(byte[] publicKey, byte[] data) throws InvalidCipherTextException {
        SM2 sm2=SM2.Instance();
        ECPoint userKey = sm2.ecc_curve.decodePoint(publicKey);
        ECPublicKeyParameters ecPublicKeyParameters=new ECPublicKeyParameters(userKey,sm2.ecc_bc_spec);
        SM2Engine sm2Engine = new SM2Engine();
        sm2Engine.init(true, new ParametersWithRandom(ecPublicKeyParameters));
        byte[] c1c2c3=sm2Engine.processBlock(data, 0, data.length);
        ////c1c2c3转c1c3c2
        byte[] c1c3c2= changeC1C2C3ToC1C3C2(c1c2c3);
        return Base64.encodeBase64String(c1c3c2);
    }
    /**
     * bc加解密使用旧标c1||c2||c3，此方法在加密后调用，将结果转化为c1||c3||c2
     * @param c1c2c3
     * @return
     */
    private static byte[] changeC1C2C3ToC1C3C2(byte[] c1c2c3) {
        final int c1Len = (x9ECParameters.getCurve().getFieldSize() + 7) / 8 * 2 + 1; //sm2p256v1的这个固定65。可看GMNamedCurves、ECCurve代码。
        final int c3Len = 32; //new SM3Digest().getDigestSize();
        byte[] result = new byte[c1c2c3.length];
        System.arraycopy(c1c2c3, 0, result, 0, c1Len); //c1
        System.arraycopy(c1c2c3, c1c2c3.length - c3Len, result, c1Len, c3Len); //c3
        System.arraycopy(c1c2c3, c1Len, result, c1Len + c3Len, c1c2c3.length - c1Len - c3Len); //c2
        return result;
    }

    /**
     * SM2 解密
     * @param privateKey
     * @param encryptedData
     * @return
     * @throws IOException
     */
    public static byte[] decrypt(byte[] privateKey, byte[] encryptedData) throws IOException
    {
        //c1c3c2转为c1c2c3
        encryptedData= changeC1C3C2ToC1C2C3(encryptedData);
        SM2 sm2=SM2.Instance();
        BigInteger userD = new BigInteger(1, privateKey);
        ECPrivateKeyParameters ecPrivateKeyParameters=new ECPrivateKeyParameters(userD,sm2.ecc_bc_spec);
        SM2Engine sm2Engine = new SM2Engine();
        sm2Engine.init(false, ecPrivateKeyParameters);
        try {
            return sm2Engine.processBlock(encryptedData, 0, encryptedData.length);
        } catch (InvalidCipherTextException e) {
            throw new RuntimeException(e);
        }
    }
    /**
     * bc加解密使用旧标c1||c3||c2，此方法在解密前调用，将密文转化为c1||c2||c3再去解密
     * @param c1c3c2
     * @return
     */
    private static byte[] changeC1C3C2ToC1C2C3(byte[] c1c3c2) {
        final int c1Len = (x9ECParameters.getCurve().getFieldSize() + 7) / 8 * 2 + 1; //sm2p256v1的这个固定65。可看GMNamedCurves、ECCurve代码。
        final int c3Len = 32; //new SM3Digest().getDigestSize();
        byte[] result = new byte[c1c3c2.length];
        System.arraycopy(c1c3c2, 0, result, 0, c1Len); //c1: 0->65
        System.arraycopy(c1c3c2, c1Len + c3Len, result, c1Len, c1c3c2.length - c1Len - c3Len); //c2
        System.arraycopy(c1c3c2, c1Len, result, c1c3c2.length - c3Len, c3Len); //c3
        return result;
    }
}
